package com.bingo.datasecurity.handler;

import cn.hutool.core.util.StrUtil;
import com.bingo.datasecurity.annotation.*;
import com.bingo.datasecurity.util.SecretUtil;
import org.apache.ibatis.binding.MapperMethod;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.List;

/**
 * 加密处理类，主要负责对加密注解的判断
 * @author bingo
 * @date 2024/2/4 9:05
 */
public class SecretHandler {


    /**
     * 对数据加/解密
     *
     * @param paramEntity 参数实体
     * @param doDecrypt   是否是解密操作
     * @throws IllegalAccessException
     */
    public static void doEncrypt(String methodPath, Object paramEntity, boolean doDecrypt) throws IllegalAccessException, ClassNotFoundException {
        //获取参数类的class
        Class<?> paramEntityClass = paramEntity.getClass();
        //如果当前是加密并且方法入参不是实体类，那么看是方法上是否有加密参数注解
        if (!doDecrypt && paramEntityClass.equals(MapperMethod.ParamMap.class)) {
            //获取类和方法名段断点索引
            int lastIndexOf = methodPath.lastIndexOf(".");
            //获取执行的类路径
            Class<?> methodClass = Class.forName(methodPath.substring(0, lastIndexOf));
            //执行的获取方法名
            String methodName = methodPath.substring(lastIndexOf + 1);
            //获取所有执行的方法，找到当前执行的方法
            Method[] declaredMethods = methodClass.getDeclaredMethods();
            //定义当前执行的加密方法
            Method currentEncryptMethod = null;
            //循环所有方法，找到当前执行的加密方法
            for (Method declaredMethod : declaredMethods) {
                //如果当前执行的方法带有加密注解，则获取执行的参数
                if (methodName.equals(declaredMethod.getName()) && declaredMethod.isAnnotationPresent(EncryptMethod.class)) {
                    //找到了当前需要加密的方法，跳出循环
                    currentEncryptMethod = declaredMethod;
                    break;
                }
            }
            if (currentEncryptMethod != null) {
                //获取所有参数
                Parameter[] parameters = currentEncryptMethod.getParameters();
                //遍历该方法的所有参数
                for (Parameter parameter : parameters) {
                    //对带有加密参数注解的参数进行加密处理
                    if (parameter.isAnnotationPresent(EncryptParam.class)) {
                        //定义加密查询的变量名
                        String paramName;
                        //定义原始参数值
                        Object paramValue;
                        //获取参数类型
                        Class<?> parameterType = parameter.getType();
                        //通过注解获取参数值
                        EncryptParam encryptParam = parameter.getAnnotation(EncryptParam.class);
                        //获取加密的参数名，要和mybatis的xml使用的sql一致
                        String encryptParamName = encryptParam.value();
                        //如果没有设置的话，尝试取默认参数名（args0、args1、args2...）
                        if (StrUtil.isBlank(encryptParamName)) {
                            //获取加密查询的变量名
                            paramName = parameter.getName();
                            //获取原始值
                            paramValue = ((MapperMethod.ParamMap) paramEntity).get(parameter.getName());
                        } else {
                            //如果传入的参数实体包含注解设置的名称，则可以获取传入的值
                            if (((MapperMethod.ParamMap) paramEntity).containsKey(encryptParamName)) {
                                //获取加密查询的变量名
                                paramName = encryptParamName;
                                //获取原始值
                                paramValue = ((MapperMethod.ParamMap) paramEntity).get(encryptParamName);
                            } else {
                                //如果不包含就抛异常
                                throw new RuntimeException("EncryptParam注解设置的参数名【" + encryptParamName + "】不在mybatis查询的参数当中，请检查！");
                            }
                        }
                        //原始值为空直接跳过
                        if (paramValue == null) {
                            continue;
                        }
                        //如果是字符串，则直接对字符串的值进行加密
                        if (String.class.equals(parameterType)) {
                            //原始值判空
                            if (StrUtil.isNotBlank(paramValue.toString())) {
                                //对值加密
                                ((MapperMethod.ParamMap) paramEntity).put(paramName, SecretUtil.encrypt(paramValue.toString()));
                            }
                        } else if (List.class.equals(parameterType)) {
                            //如果是集合，则遍历集合，看实体类的参数是否有加密注解
                            for (Object object : (List) paramValue) {
                                if (object != null) {
                                    //对实体类加密处理
                                    encryptByEntity(object, false);
                                }
                            }
                        }
                    }
                }
            }
        } else {
            //如果查询参数是实体，则进行对实体进行判定并加密
            encryptByEntity(paramEntity, doDecrypt);
        }
    }

    /**
     * 实体类加密处理
     *
     * @param paramEntity 加密实体类
     * @param doDecrypt   是否是解密
     * @throws IllegalAccessException
     */
    private static void encryptByEntity(Object paramEntity, boolean doDecrypt) throws IllegalAccessException {
        //获取实体类的class对象
        Class<?> paramEntityClass = paramEntity.getClass();
        //如果是实体类，判断该实体类是否添加EncryptEntity注解，如果没有EncryptEntity注解，则不需要加密操作
        boolean needEncryptEntity = paramEntityClass.isAnnotationPresent(EncryptEntity.class);
        //不需要加密，直接返回
        if (!needEncryptEntity) {
            return;
        }
        //是否自动标记字段
        boolean autoMark = paramEntityClass.getAnnotation(EncryptEntity.class).autoMark();
        //获取实体类的所有字段值
        Field[] declaredFields = paramEntityClass.getDeclaredFields();
        //遍历字段加密，如果自动标记为true则除了忽略的所有字段全部需要加密
        if (autoMark) {
            for (Field declaredField : declaredFields) {
                //判断字段类型是否是字符串，不是字符串直接跳过
                Class<?> fieldType = declaredField.getType();
                if (!String.class.equals(fieldType)) {
                    continue;
                }
                //判断字段是否有忽略加密注解
                boolean ignoreEncrypt = declaredField.isAnnotationPresent(IgnoreEncryptField.class);
                if (ignoreEncrypt) {
                    continue;
                }
                encryptField(paramEntity, doDecrypt, declaredField);
            }
        } else {
            //否则只对带有EncryptField字段的字段加密
            for (Field declaredField : declaredFields) {
                //判断字段类型是否是字符串，不是字符串直接跳过
                Class<?> fieldType = declaredField.getType();
                if (!String.class.equals(fieldType)) {
                    continue;
                }
                //判断是否有加密注解
                boolean needEncryptField = declaredField.isAnnotationPresent(EncryptField.class);
                if (!needEncryptField) {
                    continue;
                }
                encryptField(paramEntity, doDecrypt, declaredField);
            }
        }
    }

    /**
     * 加密/解密字段
     *
     * @param paramEntity   参数实体
     * @param doDecrypt     是否是解密操作
     * @param declaredField 待处理字段
     * @throws IllegalAccessException
     */
    private static void encryptField(Object paramEntity, boolean doDecrypt, Field declaredField) throws IllegalAccessException {
        //如果自动标记字段则除了忽略的所有字段全部需要加密
        declaredField.setAccessible(true);
        //获取原始值
        Object paramValue = declaredField.get(paramEntity);
        //加解密操作
        if (paramValue != null && StrUtil.isNotBlank(paramValue.toString())) {
            if (doDecrypt) {
                //设置解密值
                try {
                    declaredField.set(paramEntity, SecretUtil.decrypt(paramValue.toString()));
                } catch (Exception e) {
                    throw new RuntimeException("解密失败，异常信息为【" + e.getMessage() + "】，请联系管理员");
                }
            } else {
                //设置加密值
                declaredField.set(paramEntity, SecretUtil.encrypt(paramValue.toString()));
            }
            declaredField.setAccessible(false);
        }
    }


}
